using System;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Dapper.ExpressionExtension.Resolvers;
using Dapper.ExpressionExtension.SqlBuilders;

namespace Dapper.ExpressionExtension.ExpressionMappers
{
    public static partial class ExpressionMapper
    {
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="connection"></param>
        /// <param name="entity"></param>
        /// <param name="transaction"></param>
        /// <returns></returns>
        public static bool Delete<TEntity>(this IDbConnection connection, TEntity entity, IDbTransaction transaction = null)
        {
            var sql = BuildDeleteQuery(GetSqlBuilder(connection), typeof(TEntity));
            LogQuery<TEntity>(sql);
            return connection.Execute(sql, entity, transaction) > 0;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="connection"></param>
        /// <param name="entity"></param>
        /// <param name="transaction"></param>
        /// <returns></returns>
        public static async Task<bool> DeleteAsync<TEntity>(this IDbConnection connection, TEntity entity, IDbTransaction transaction = null)
        {
            var sql = BuildDeleteQuery(GetSqlBuilder(connection), typeof(TEntity));
            LogQuery<TEntity>(sql);
            return await connection.ExecuteAsync(sql, entity, transaction) > 0;
        }

        internal static string BuildDeleteQuery(ISqlBuilder sqlBuilder, Type type)
        {
            var cacheKey = new QueryCacheKey(QueryCacheType.Delete, sqlBuilder, type);
            if (QueryCache.TryGetValue(cacheKey, out var sql))
            {
                return sql;
            }
            var tableName = ResolverHelper.Table(type, sqlBuilder);
            var keyProperties = ResolverHelper.KeyProperties(type);
            var whereClauses = keyProperties.Select(p => $"{ResolverHelper.Column(p.Property, sqlBuilder)} = {sqlBuilder.PrefixParameter(p.Property.Name)}");

            sql = $"delete from {tableName} where {string.Join(" and ", whereClauses)}";

            QueryCache.TryAdd(cacheKey, sql);

            return sql;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="connection"></param>
        /// <param name="predicate"></param>
        /// <param name="transaction"></param>
        /// <returns></returns>
        public static int DeleteMultiple<TEntity>(this IDbConnection connection, Expression<Func<TEntity, bool>> predicate, IDbTransaction transaction = null)
        {
            var sql = BuildDeleteMultipleQuery(GetSqlBuilder(connection), predicate, out var parameters);
            LogQuery<TEntity>(sql);
            return connection.Execute(sql, parameters, transaction);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="connection"></param>
        /// <param name="predicate"></param>
        /// <param name="transaction"></param>
        /// <returns></returns>
        public static async Task<int> DeleteMultipleAsync<TEntity>(this IDbConnection connection, Expression<Func<TEntity, bool>> predicate, IDbTransaction transaction = null)
        {
            var sql = BuildDeleteMultipleQuery(GetSqlBuilder(connection), predicate, out var parameters);
            LogQuery<TEntity>(sql);
            return await connection.ExecuteAsync(sql, parameters, transaction);
        }

        private static string BuildDeleteMultipleQuery<TEntity>(ISqlBuilder sqlBuilder, Expression<Func<TEntity, bool>> predicate, out DynamicParameters parameters)
        {
            var type = typeof(TEntity);
            var sql = BuildDeleteAllQuery(sqlBuilder, type);

            sql += CreateSqlExpression<TEntity>(sqlBuilder)
                .Where(predicate)
                .ToSql(out parameters);
            return sql;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="connection"></param>
        /// <param name="transaction"></param>
        /// <returns></returns>
        public static int DeleteAll<TEntity>(this IDbConnection connection, IDbTransaction transaction = null)
        {
            var sql = BuildDeleteAllQuery(GetSqlBuilder(connection), typeof(TEntity));
            LogQuery<TEntity>(sql);
            return connection.Execute(sql, transaction: transaction);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TEntity"></typeparam>
        /// <param name="connection"></param>
        /// <param name="transaction"></param>
        /// <returns></returns>
        public static async Task<int> DeleteAllAsync<TEntity>(this IDbConnection connection, IDbTransaction transaction = null)
        {
            var sql = BuildDeleteAllQuery(GetSqlBuilder(connection), typeof(TEntity));
            LogQuery<TEntity>(sql);
            return await connection.ExecuteAsync(sql, transaction: transaction);
        }

        internal static string BuildDeleteAllQuery(ISqlBuilder sqlBuilder, Type type)
        {
            var cacheKey = new QueryCacheKey(QueryCacheType.DeleteAll, sqlBuilder, type);
            if (!QueryCache.TryGetValue(cacheKey, out var sql))
            {
                var tableName = ResolverHelper.Table(type, sqlBuilder);
                sql = $"delete from {tableName}";
                QueryCache.TryAdd(cacheKey, sql);
            }

            return sql;
        }
    }
}
